﻿// -----------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition.ReflectionModel;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.Linq;
using System.Reflection;
using Microsoft.Internal;
using Microsoft.Internal.Collections;

namespace System.ComponentModel.Composition.AttributedModel
{
    internal class AttributedComposablePartDefinition : ReflectionComposablePartDefinition
    {
        private readonly Type _partType;
        private readonly bool _ignoreConstructors;
        private CompositionOptionsAttribute _compositionOptions;
        private ConstructorInfo _constructor;
        private IDictionary<string, object> _metadata;
        private IEnumerable<ExportDefinition> _exportDefinitions;
        private IEnumerable<ImportDefinition> _importDefinitions;
        private bool _hasImportingConstructor;

        public AttributedComposablePartDefinition(Type partType, ICompositionElement origin)
            : this(partType, false, (CompositionOptionsAttribute)null, origin)
        {
        }

        public AttributedComposablePartDefinition(Type partType, bool ignoreConstructors, ICompositionElement origin)
            : this(partType, ignoreConstructors, (CompositionOptionsAttribute)null, origin)
        {

        }

        public AttributedComposablePartDefinition(Type partType, bool ignoreConstructors, CompositionOptionsAttribute compositionOptions, ICompositionElement origin) 
            : base(origin)
        {
            Requires.NotNull(partType, "partType");

            this._partType = partType;
            this._ignoreConstructors = ignoreConstructors;
            this._compositionOptions = compositionOptions;
        }

        public override IDictionary<string, object> Metadata
        {
            get
            {
                if (this._metadata == null)
                {
                    this._metadata = this._partType.GetPartMetadataForType(this.CompositionOptions);
                }

                return this._metadata;
            }
        }

        public override IEnumerable<ExportDefinition> ExportDefinitions
        {
            get
            {
                if (this._exportDefinitions == null)
                {
                    List<ExportDefinition> exports = new List<ExportDefinition>();
                    foreach (MemberInfo member in this.GetExportMembers(true))
                    {
                        foreach (ExportAttribute exportAttribute in member.GetAttributes<ExportAttribute>(!(member is Type)))
                        {
                            string contractName = member.GetContractNameFromExport(exportAttribute);
                            exports.Add(new AttributedMemberExportDefinition(this, contractName, member));
                        }
                    }

                    this._exportDefinitions = exports;
                }
                return this._exportDefinitions;
            }
        }

        public override IEnumerable<ImportDefinition> ImportDefinitions
        {
            get
            {
                if (this._importDefinitions == null)
                {
                    List<ImportDefinition> imports = new List<ImportDefinition>();
                    foreach (MemberInfo member in this.GetImportMembers(true))
                    {
                        imports.Add(new AttributedMemberImportDefinition(this, member));
                    }

                    if (!this._ignoreConstructors)
                    {
                        if (this.Constructor != null)
                        {
                            foreach (ParameterInfo parameter in this.Constructor.GetParameters())
                            {
                                imports.Add(new AttributedParameterImportDefinition(this, parameter));
                            }
                        }
                    }

                    this._importDefinitions = imports;
                }
                return this._importDefinitions;
            }
        }

        public override ComposablePart CreatePart()
        {
            return new AttributedComposablePart((AttributedComposablePartDefinition)this);
        }

        public override Type PartType
        {
            get { return this._partType; }
        }

        public bool IsDiscoverable()
        {
            // This allows static classes with static exports
            if (this._partType.IsAbstract && !this._partType.IsSealed)
            {
                return false;
            }

            if (this._partType.ContainsGenericParameters)
            {
                return false;
            }

            switch (this.CompositionOptions.CatalogDiscoveryMode)
            {
                case CatalogDiscoveryMode.Auto:
                    return this.ContainsNonInheritedImportsOrExports();

                case CatalogDiscoveryMode.Never:
                    return false;

                default:
                    Assumes.IsTrue(this.CompositionOptions.CatalogDiscoveryMode == CatalogDiscoveryMode.Always);
                    return true;
            }
        }

        public override ConstructorInfo Constructor
        {
            get
            {
                EnsureConstructor();
                return this._constructor;
            }
        }

        private CompositionOptionsAttribute CompositionOptions
        {
            get
            {
                if (this._compositionOptions == null)
                {
                    this._compositionOptions = this._partType.GetFirstAttribute<CompositionOptionsAttribute>() ?? CompositionOptionsAttribute.Default;
                }
                return this._compositionOptions;
            }
        }

        private BindingFlags GetBindingFlags(bool inherit)
        {
            BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;

            if (!this._partType.IsAbstract)
            {
                flags |= BindingFlags.Instance;
            }

            if (!inherit)
            {
                flags |= BindingFlags.DeclaredOnly;
            }

            return flags;
        }

        private IEnumerable<MemberInfo> GetExportMembers(bool inherit)
        {
            BindingFlags flags = this.GetBindingFlags(inherit);

            if (IsExport(this._partType, false))
            {
                yield return this._partType;
            }

            // Walk the inheritance chain if needed.
            if (inherit)
            {
                for (Type type = this._partType.BaseType; type != null; type = type.BaseType)
                {
                    if (IsExport(type, false))
                    {
                        yield return type;
                    }
                }
            }

            // BUG : are we doing the right thing here? what happens to non-public members in the base classes? Reflection will not return them.

            // Walk the fields 
            foreach (var member in this._partType.GetFields(flags).Where(IsExport))
            {
                yield return member;
            }

            // Walk the properties 
            foreach (var member in this._partType.GetProperties(flags).Where(IsExport))
            {
                yield return member;
            }

            // Walk the methods 
            foreach (var member in this._partType.GetMethods(flags).Where(IsExport))
            {
                yield return member;
            }
        }

        private IEnumerable<MemberInfo> GetImportMembers(bool inherit)
        {
            BindingFlags flags = this.GetBindingFlags(inherit);

            // BUG : are we doing the right thing here? what happens to non-public members in the base classes? Reflection will not return them.

            // Walk the fields 
            foreach (var member in this._partType.GetFields(flags).Where(IsImport))
            {
                yield return member;
            }

            // Walk the properties 
            foreach (var member in this._partType.GetProperties(flags).Where(IsImport))
            {
                yield return member;
            }
        }

        private static bool IsExport(ICustomAttributeProvider attributeProvider, bool inherit)
        {
            return attributeProvider.IsAttributeDefined<ExportAttribute>(inherit);
        }

        private static bool IsExport(ICustomAttributeProvider attributeProvider)
        {
            return IsExport(attributeProvider, true);
        }

        private static bool IsImport(ICustomAttributeProvider attributeProvider)
        {
            return attributeProvider.IsAttributeDefined<ImportAttribute>();
        }

        private bool ContainsNonInheritedImportsOrExports()
        {
            if (this.GetExportMembers(false).Any() || 
                this.GetImportMembers(false).Any())
            {
                return true;
            }

            EnsureConstructor();
            return this._hasImportingConstructor;
        }

        private void EnsureConstructor()
        {
            if (this._partType.IsAbstract || this._constructor != null)
            {
                return;
            }

            this._constructor = SelectConstructor();
        }

        private ConstructorInfo SelectConstructor()
        {
            // Only deal with non-static constructors
            BindingFlags flags = this.GetBindingFlags(false) & ~BindingFlags.Static;

            ConstructorInfo[] constructors = this._partType.GetConstructors(flags);

            // Should likely only happen for static or abstract types
            if (constructors.Length == 0)
            {
                return null;
            }

            // Select the marked constructor if there is exactly one marked
            IEnumerable<ConstructorInfo> importingConstructors = constructors.Where(
                constructor => constructor.IsAttributeDefined<ImportingConstructorAttribute>());

            switch (importingConstructors.GetCardinality())
            {
                case EnumerableCardinality.One:
                    this._hasImportingConstructor = true;
                    return importingConstructors.First();

                case EnumerableCardinality.TwoOrMore:
                    this._hasImportingConstructor = true;
                    // Return null, the part will error on instantiation.
                    return null;
            }

            this._hasImportingConstructor = false;

            // If there are no marked constructors then select the default constructor
            IEnumerable<ConstructorInfo> defaultConstructors = constructors.Where(
                constructor => constructor.GetParameters().Length == 0);

            // There should only ever be zero or one default constructors            
            return defaultConstructors.SingleOrDefault();   
        }
    }
}
